package com.zkh.myutils.database.dbpool;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.zkh.myutils.database.DatabaseException;
import com.zkh.myutils.database.conutils.PoolUtils;
import com.zkh.myutils.io.log.Logger;

/**
 * 池子对象
 * @author zkh
 */
public final class Pools {
	//使用中的连接对象
	ConcurrentLinkedQueue<Connection> used = new ConcurrentLinkedQueue<>();
	//空闲的连接对象
	ConcurrentLinkedQueue<Connection> idle = new ConcurrentLinkedQueue<>();
	//唯一标识
	String key;
	//暂时使用默认数据源
	private DataBaseInfo dataBaseInfo;
	//池记录
	static Map<String, Pools> poolsMap = new HashMap<>();
	//驱动加载情况
	private static Map<String, Boolean> typeLoaded = new HashMap<>();
	
	static {
		// 执行连接是否有效的检查
		PoolTimer.runUsefulCheck();
		//执行连接池连接回收操作
		PoolTimer.runRecycle();
	}
	
	/**
	 * 初始化连接池
	 * @param key 数据源唯一标识
	 */
	public Pools(String key) throws SQLException {
		//保存标识
		this.key = key;
		//保存数据库信息
		this.dataBaseInfo = ConnectConfigure.getSourceConfigById(key);
		//数据库类型
		String dbtype = this.dataBaseInfo.getDbtype().getDbType();
		//未加载过驱动，则加载驱动
		if(!typeLoaded.getOrDefault(dbtype, false)) {
			//注册驱动
			try {
				Class.forName(dataBaseInfo.getDriver());
				//保存状态
				typeLoaded.put(dbtype, true);
			} catch (ClassNotFoundException e) {
				throw new DatabaseException("数据库驱动加载失败，请检查驱动包是否正确，或检查驱动配置是否正确！", e);
			}
		}
		//显示日志
		if(ConnectConfigure.isShowPoolLog()) {
			Logger.debug("开始初始化连接");
		}
		//默认数据
		for(int i=0;i<ConnectConfigure.getPoolIniSize();i++) {
			//保存连接
			idle.add(DriverManager.getConnection(dataBaseInfo.getUrl(), dataBaseInfo.getUsername(), dataBaseInfo.getPassword()));
		}
		//显示日志
		if(ConnectConfigure.isShowPoolLog()) {
			Logger.debug("初始化连接完毕，共创建{}个连接", ConnectConfigure.getPoolIniSize());
		}
		//保存当前池对象
		poolsMap.put(key, this);
	}
	
	/**
	 * 获取一个连接
	 */
	public Connection getConnection() {
		//有空闲连接
		if(!idle.isEmpty()) {
			return getWraper();
		}
		//没有空闲连接，检查连接池是否达到上限
		if(used.size()>=ConnectConfigure.getPoolMaxSize()) {
			throw new DatabaseException("连接池已达到上限，请稍候再试！");
		}
		/*** 新增若干个新连接 ***/
		//新增配置项为0
		if(ConnectConfigure.getPoolStep()<=0) {
			throw new DatabaseException("连接已取用完，不能再新增连接，请稍候再试！");
		}
		//创建连接
		incrementConnection();
		//获取一个连接
		return getWraper();
	}
	
	/**
	 * 连接池中的连接已使用完，按配置的递增数量创建新连接
	 */
	private synchronized void incrementConnection() {
		//没有空闲连接，则创建（若多个线程同时等待，只需要第一个线程创建连接即可）
		if(idle.isEmpty()) {
			try {
				//可新增连接总数量
				int couldIncreaseNumber = ConnectConfigure.getPoolMaxSize()-used.size();
				//实际新增数量
				int increaseNumber = couldIncreaseNumber>=ConnectConfigure.getPoolStep() ? 
						ConnectConfigure.getPoolStep() :  ConnectConfigure.getPoolStep() - couldIncreaseNumber;
				//显示日志
				if(ConnectConfigure.isShowPoolLog()) {
					Logger.debug("连接已用完，开始新增连接");
				}
				//新增若干个新连接
				for(int i=0;i<increaseNumber;i++) {
					//新增连接
					idle.add(DriverManager.getConnection(dataBaseInfo.getUrl(), dataBaseInfo.getUsername(), dataBaseInfo.getPassword()));
				}
				//添加到连接池连接回收操作队列
				PoolTimer.addRunRecycle(this);
				//显示日志
				if(ConnectConfigure.isShowPoolLog()) {
					Logger.debug("本次共新增{}个连接，当前连接池共存在{}个连接", increaseNumber, used.size() + idle.size());
				}
			} catch (SQLException e) {
				throw new DatabaseException(e);
			}
		}
	}
	
	/**
	 * 释放连接
	 */
	public void release(Connection connection) {
		//是包装连接对象
		if(connection instanceof ConnectWapper) {
			//还原连接
			ConnectWapper wapper = (ConnectWapper) connection;
			//销毁ConnectWapper对象与Connection对象的联系，否则连接返还连接池后还是能够使用
			Connection conn = wapper.removeOriginConnection();
			//使用中，则移除，避免用户多次调用close
			if(used.remove(conn)) {
				//放入空闲列表
				idle.add(conn);
			}
		}
	}
	
	/**
	 * 调试方法
	 */
	public void showDebug() {
		Logger.debug("使用中的连接数量：{}", used.size());
		Logger.debug("空闲中的连接数量：{}", idle.size());
	}
	
	/**
	 * 获取数据库连接信息
	 */
	public Connection getConnection(String username, String password) throws SQLException {
		return DriverManager.getConnection(dataBaseInfo.getUrl(), username, password);
	}
	
	/**
	 * 获取数据源配置信息
	 */
	public DataBaseInfo getDataBaseInfo() {
		return this.dataBaseInfo;
	}
	
	/**
	 * 执行定时器检查
	 * @param type 检查类型。1：连接回收；2：连接是否失效
	 */
	public static synchronized int runCheck(Pools pools, int type) {
		//连接回收
		if(type==1) {
			return pools.runRecycle();
		}
		//连接是否失效
		if(type==2) {
			return pools.runUsefulCheck();
		}
		//其它
		return 0;
	}
	
	/**
	 * 销毁池子
	 */
	public static void destoryPools() {
		try {
			//停掉定时任务
			PoolTimer.stopTimer();
			//销毁池子
			for(Pools p: poolsMap.values()) p.destroyPool();
			poolsMap.clear();
			//卸载驱动
			Enumeration<Driver> drivers = DriverManager.getDrivers();
			//遍历卸载
			while(drivers.hasMoreElements()) {
				DriverManager.deregisterDriver(drivers.nextElement());
			}
			//如果引入了MySQL包
			try {
				Class<?> abcls = Class.forName("com.mysql.jdbc.AbandonedConnectionCleanupThread");
				//调用uncheckedShutdown方法，防止停用服务时报错
				abcls.getMethod("uncheckedShutdown").invoke(null);
			}catch(ClassNotFoundException e) {
				//不存在，停止即可
			}
		}catch(Exception e) {
			Logger.errors("释放连接池时出现错误", e);
		}
	}
	
	/**
	 * 销毁连接池
	 */
	private void destroyPool() throws SQLException {
		/**关闭连接**/
		for(Connection c: used) {
			c.close();
		}
		for(Connection c: idle) {
			c.close();
		}
		/**清空池子**/
		used.clear();idle.clear();
	}
	
	/**
	 * 执行连接池连接回收操作
	 * @return 本次回收连接数量
	 */
	private int runRecycle() {
		//移除数量
		int removeNumber = 0;
		//空闲连接不超过3个，或没有超过连接池初始容量，不用回收连接
		if(idle.size()<=3 || ConnectConfigure.getPoolIniSize()>=used.size() + idle.size()) {
			return removeNumber;
		}
		/** 超过初始容量 **/
		//理论可释放数量
		int release = used.size() + idle.size() - ConnectConfigure.getPoolIniSize();
		//实际可释放数量
		release = Math.min(release, ConnectConfigure.getPoolStep());
		//查询空闲状态数量
		release = Math.min(release, idle.size());
		//开始释放
		while(release>0) {
			// 移除第一个
			Connection conn = idle.remove();
			//不为null
			if(conn!=null) {
				// 关闭连接
				PoolUtils.close(conn);
				//可释放数量递减
				release--;
				//移除数量加1
				removeNumber++;
			}
		}
		//返回释放数量
		return removeNumber;
	}
	
	/**
	 * 执行连接是否有效的检查
	 * @return 返回失效被移除的连接数量
	 */
	private int runUsefulCheck() {
		//移除数量
		int[] removeNumber = {0};
		//检查并移除失败的连接
		idle.removeIf(conn->{
			//检查连接是否有效
			try {
				//失效
				if(!conn.isValid(5)) {
					//失效，则不再还回连接池（大于-1仅仅是为了返回true）
					return removeNumber[0]++ >-1;
				}
			}catch (SQLException e) {}
			//不移除
			return false;
		});
		//返回移除数量
		return removeNumber[0];
	}

	/**
	 * 获取包装连接，并处理容器
	 */
	private Connection getWraper() {
		try {
			//从空闲链表中移除第一个连接，用来使用
			Connection conn = idle.remove();
			//添加到使用状态
			used.add(conn);
			//返回包装后的连接
			return new ConnectWapper(conn, this);
		}catch(NoSuchElementException e) {
			//没有可用连接，让连接池重新生成
			return getConnection();
		}
	}
}
